%matplotlib inline
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from PIL import Image, ImageEnhance
import matplotlib.image as mpimg
from tqdm import tqdm
import shutil
from IPython.display import HTML, display
# save plots
save_plots = False
path_plots = './plots/'
# write cv-split as csv
split_cv_csv = False
Kurz Übersicht welche Erkenntnisse aus der EDA-Analyse gezogen werden können:
Die CSV's beinhalten die Bild ID, Bildpfad, Aufnahmeort ID und die Tierklassifikation (Training)
train_features = pd.read_csv("../competition_data/train_features.csv", index_col="id")
test_features = pd.read_csv("../competition_data/test_features.csv", index_col="id")
train_labels = pd.read_csv("../competition_data/train_labels.csv", index_col="id")
display(train_features.head())
display(train_labels.head())
| filepath | site | |
|---|---|---|
| id | ||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 |
| ZJ000002 | train_features/ZJ000002.jpg | S0009 |
| ZJ000003 | train_features/ZJ000003.jpg | S0008 |
| ZJ000004 | train_features/ZJ000004.jpg | S0036 |
| antelope_duiker | bird | blank | civet_genet | hog | leopard | monkey_prosimian | rodent | |
|---|---|---|---|---|---|---|---|---|
| id | ||||||||
| ZJ000000 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| ZJ000001 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
| ZJ000002 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| ZJ000003 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
| ZJ000004 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
# reverse One-Hot-Encoding
species_labels = sorted(train_labels.columns.unique())
train_labels_cat = train_labels.copy()
train_labels_cat['label'] = train_labels[species_labels].idxmax(axis=1)
train_labels_cat = train_labels_cat.drop(species_labels, axis=1)
display(train_labels_cat.head())
| label | |
|---|---|
| id | |
| ZJ000000 | bird |
| ZJ000001 | monkey_prosimian |
| ZJ000002 | bird |
| ZJ000003 | monkey_prosimian |
| ZJ000004 | leopard |
train_features_label = train_features.merge(train_labels_cat, left_index=True, right_index=True)
train_features_label
| filepath | site | label | |
|---|---|---|---|
| id | |||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 | bird |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 | monkey_prosimian |
| ZJ000002 | train_features/ZJ000002.jpg | S0009 | bird |
| ZJ000003 | train_features/ZJ000003.jpg | S0008 | monkey_prosimian |
| ZJ000004 | train_features/ZJ000004.jpg | S0036 | leopard |
| ... | ... | ... | ... |
| ZJ016483 | train_features/ZJ016483.jpg | S0093 | blank |
| ZJ016484 | train_features/ZJ016484.jpg | S0043 | leopard |
| ZJ016485 | train_features/ZJ016485.jpg | S0089 | civet_genet |
| ZJ016486 | train_features/ZJ016486.jpg | S0095 | bird |
| ZJ016487 | train_features/ZJ016487.jpg | S0021 | civet_genet |
16488 rows × 3 columns
Klassifikation findet für 8 Tierarten statt
print(species_labels)
print(f'Anzahl Tierklassen: {len(species_labels)}')
['antelope_duiker', 'bird', 'blank', 'civet_genet', 'hog', 'leopard', 'monkey_prosimian', 'rodent'] Anzahl Tierklassen: 8
img = mpimg.imread('class_images/class_images.jpg')
plt.imshow(img)
plt.axis('off')
plt.show()
Die Klassen 'bird', 'rodent' umfassen mehrere Tierarten. Die Klasse 'blank' steht für ein Bild ohne ein Tier. Die übrigen Tierklassen sind im obigen Bild enthalten (Tierklassenbilder quelle: google).
Probeansicht Bilder (quelle: benchmark file)
random_state = 42
path_img = '../competition_data/'
# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))
# iterate through each species
for species, ax in zip(species_labels, axes.flat):
# get an image ID for this species
img_id = (
train_labels[train_labels.loc[:,species] == 1]
.sample(1, random_state=random_state)
.index[0]
)
# reads the filepath and returns a numpy array
img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
# plot etc
ax.imshow(img)
ax.set_title(f"{img_id} | {species}")
Zelle mehrmals ausführen um unterschiedliche Bilder zu erhalten
# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))
# iterate through each species
for species, ax in zip(species_labels, axes.flat):
# get an image ID for this species
img_id = (
train_labels[train_labels.loc[:,species] == 1]
.sample(1)
.index[0]
)
# reads the filepath and returns a numpy array
img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
# plot etc
ax.imshow(img)
ax.set_title(f"{img_id} | {species}")
Folgend wurden spezifisch Bilder herausgesucht um die Problematik von schlechten Bilder zu zeigen.
example_bad_img = ['ZJ015580', 'ZJ002746', 'ZJ007054', 'ZJ000888', 'ZJ010341', 'ZJ014451',
'ZJ004927', 'ZJ007091', 'ZJ013234', 'ZJ013093', 'ZJ004451', 'ZJ010190',
'ZJ002138', 'ZJ002196']
example_good_img = ['ZJ003890', 'ZJ004925', 'ZJ012762', 'ZJ014396', 'ZJ015264', 'ZJ000895',
'ZJ007334', 'ZJ004978', 'ZJ014157', 'ZJ010885']
def plot_image_from_image_id(image_ids: list, nrows=4, ncols=3, figsize=(15, 15),
fontsize=10, path='../competition_data/'):
# create grid
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)
# iterate through each bad image
for idx, (img_id, ax) in enumerate(zip(image_ids, axes.flat)):
# get image label
img_label = train_labels_cat.loc[image_ids[idx]]['label']
# reads the filepath and returns a numpy array
img = mpimg.imread(path + train_features.loc[img_id].filepath)
# image dimension
width, height = Image.open(path_img + train_features.loc[img_id].filepath).size
# plot etc
ax.imshow(img)
ax.set_title(f"{img_id} | {img_label} | {str(width)}x{str(height)}", fontsize=fontsize)
plot_image_from_image_id(example_bad_img)
#plot_image_from_image_id(example_good_img)
Folgende Probleme zum Bildinhalt wurden bemerkt:
Zu Prüfen: wenn die Bilder im Notebook mit den Bilder im File Ordner verglichen werden, fällt auf dass die Bilder in der Windowsansicht schwarz-weiss im Notebook jedoch blau-gelb angezeigt werden. Beispiel 'ZJ003494'.
Interpretation von mpimg prüfen.
(Quelle: Benchmark) Wie im Benchmark Notebook beschrieben, entspricht die Tierklassen Verteilungen nicht dem eigentlichen Vorkommen. Die (Trainings)daten wurden für die Competition bereits vorbereitet.
display(train_labels.sum().sort_values(ascending=False))
display(train_labels.sum().divide(train_labels.shape[0]).sort_values(ascending=False))
monkey_prosimian 2492.0 antelope_duiker 2474.0 civet_genet 2423.0 leopard 2254.0 blank 2213.0 rodent 2013.0 bird 1641.0 hog 978.0 dtype: float64
monkey_prosimian 0.151140 antelope_duiker 0.150049 civet_genet 0.146955 leopard 0.136705 blank 0.134219 rodent 0.122089 bird 0.099527 hog 0.059316 dtype: float64
train_features.head(2)
| filepath | site | |
|---|---|---|
| id | ||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 |
print(f'Anzahl Standort Training-Kameras: {len(train_features.site.unique())}')
print(f'Anzahl Standort Test-Kameras: {len(test_features.site.unique())}')
print(f'Mittelwert: {len(train_features.site) / len(train_features.site.unique()):.0f} Bilder pro Ort, Train')
print(f'Mittelwert: {len(test_features.site) / len(test_features.site.unique()):.0f} Bilder pro Ort, Test')
Anzahl Standort Training-Kameras: 148 Anzahl Standort Test-Kameras: 51 Mittelwert: 111 Bilder pro Ort, Train Mittelwert: 88 Bilder pro Ort, Test
df_site_count = train_features.groupby('site').count().reset_index().sort_values('filepath', ascending=True)
# plot histgram
plt.figure(figsize=(20, 8))
plt.bar(df_site_count.site, df_site_count.filepath)
plt.title('Anzahl Bilder je Kamera Standort (Trainset)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90, fontsize=8)
plt.yticks(fontsize=12)
plt.grid(axis='y')
if save_plots:
plt.savefig(path_plots + 'number_images_site.png', bbox_inches="tight")
plt.show()
Insgesamt bestehen 148 verschiedene Kamera Standorte. Die ID-Nummerierung verläuft von S0001 bis S0198 (ID-Nummerierung nicht komplet durch numeriert). Die Verteilung zeigt die Anzahl Bilder die für einen Standort zur Verfügung stehen. Einige Standorte nehmen nur sehr wenige Bilder auf (1-2) ander enthalten hunderte Bilder.
df_train_feature_labels = train_features.merge(train_labels_cat, left_index=True, right_index=True)
df_train_feature_labels.head()
| filepath | site | label | |
|---|---|---|---|
| id | |||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 | bird |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 | monkey_prosimian |
| ZJ000002 | train_features/ZJ000002.jpg | S0009 | bird |
| ZJ000003 | train_features/ZJ000003.jpg | S0008 | monkey_prosimian |
| ZJ000004 | train_features/ZJ000004.jpg | S0036 | leopard |
df_stacked_plot = pd.crosstab(df_train_feature_labels['site'], df_train_feature_labels['label'])
df_stacked_plot['max_count'] = df_stacked_plot.sum(axis=1)
df_stacked_plot = df_stacked_plot.sort_values('max_count', ascending=True)
df_stacked_plot.head()
| label | antelope_duiker | bird | blank | civet_genet | hog | leopard | monkey_prosimian | rodent | max_count |
|---|---|---|---|---|---|---|---|---|---|
| site | |||||||||
| S0102 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
| S0078 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 2 |
| S0079 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 2 |
| S0178 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 2 |
| S0143 | 1 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 3 |
def plot_stacked_classes_site(stacked_plot, min_count = 50, save_plot=False):
stacked_plot = stacked_plot[stacked_plot['max_count'] > min_count]
stacked_plot = stacked_plot.drop(columns='max_count')
stacked_plot.plot(kind='bar', stacked=True, figsize=(20,8))
plt.title('Vorkommen Tierklassen je Standort (Train)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90)
plt.grid(axis='y')
if save_plots:
plt.savefig(path_plots + 'dist_animals_n_images_site.png', bbox_inches="tight")
plt.show()
plot_stacked_classes_site(df_stacked_plot, 50, save_plots)
site = 'S0196'
len(train_features[train_features['site'] == site])
15
def plot_image_from_site_id(site_id: str, nrows=4, ncols=3, figsize=(15, 15),
path='../competition_data/'):
# get images from site
img_site = train_features[train_features['site'] == site_id].reset_index()
img_site = img_site[0:nrows*ncols]
# create grid
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)
# iterate through each bad image
for img_id, image_path, ax in zip(img_site.id, img_site.filepath, axes.flat):
# reads the filepath and returns a numpy array
img = mpimg.imread(path + image_path)
# get image label
img_label = train_labels_cat.loc[img_id]['label']
# plot
ax.imshow(img)
ax.set_title(f"{img_id} | {img_label}")
plot_image_from_site_id(site)
Hier soll geprüft werden wie stark die Auflösungen der Kameras varrieren.
Die Auflösungen kann mit 'mpimg' durch shape wiedergegeben werden. Eine schneller Möglichkeit ist 'Pillow' zu verwenden, die Bittiefe muss jedoch seperate mit getbands() ausgelesen werden.
mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32}
img_id = []
img_width = []
img_height = []
img_dim = []
band_mode = []
bit_depth = []
for imag_id in train_features.reset_index().id:
# get id and dimensions
img = Image.open(path_img + train_features.loc[imag_id].filepath)
width, height = img.size
dim = img.size
mode = img.getbands()
# save to list
img_id.append(imag_id)
img_width.append(width)
img_height.append(height)
img_dim.append(dim)
band_mode.append(mode)
df_img_shape_pil = pd.DataFrame({'id': img_id, 'width': img_width, 'height': img_height,
'dim':img_dim, 'band_mode': band_mode})
df_img_shape_pil.head(2)
| id | width | height | dim | band_mode | |
|---|---|---|---|---|---|
| 0 | ZJ000000 | 960 | 540 | (960, 540) | (R, G, B) |
| 1 | ZJ000001 | 960 | 540 | (960, 540) | (R, G, B) |
unique_dim = df_img_shape_pil.dim.value_counts().reset_index().sort_values('dim', ascending=False)
unique_dim = unique_dim.rename(columns={'dim': 'count', 'index': 'dim'})
unique_dim['count_relativ'] = np.round(unique_dim['count'] / sum(unique_dim['count']), 2)
display(unique_dim.head())
| dim | count | count_relativ | |
|---|---|---|---|
| 0 | (640, 360) | 7490 | 0.45 |
| 1 | (960, 540) | 6345 | 0.38 |
| 2 | (640, 335) | 970 | 0.06 |
| 3 | (360, 240) | 864 | 0.05 |
| 4 | (960, 515) | 458 | 0.03 |
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
fontsize = 8
ax[0].bar(np.arange(len(unique_dim)), unique_dim['count'])
ax[0].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[0].set_title('Verteilung der Bildauflösungen')
ax[0].set_ylabel('counts', fontsize=fontsize)
ax[0].tick_params(axis='y', labelsize=fontsize)
ax[0].set_xticklabels(unique_dim.dim, rotation=60)
for i, v in enumerate(unique_dim['count']):
ax[0].text(i, v, str(v), ha='center', va='bottom', fontsize=fontsize)
ax[1].bar(np.arange(len(unique_dim)), unique_dim['count_relativ'] )
ax[1].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[1].set_title('Verteilung der Bildauflösungen [relativ]')
ax[1].set_ylabel('counts', fontsize=fontsize)
ax[1].tick_params(axis='y', labelsize=fontsize)
ax[1].set_xticklabels(unique_dim.dim, rotation=60)
for i, v in enumerate(unique_dim['count_relativ']):
ax[1].text(i, v, str(v*100)+'%', ha='center', va='bottom', fontsize=fontsize)
if save_plots:
plt.savefig(path_plots + 'dist_image_resolution.png', bbox_inches="tight")
plt.show()
Über 80% der Bilder haben die Auflösung (640, 360) oder (960, 540)
Die Bittiefe beschreibt wie viele Bits zur Darstellung von Farben eines Pixels zur Verfügung stehen. Standard ist 24Bit, 8Bit für jeden Farbkanal von RGB.
df_bit_depth = df_img_shape_pil['band_mode'].value_counts().reset_index(name='count')
df_bit_depth = df_bit_depth.rename(columns={'index':'mode'})
df_bit_depth['count_rel'] = df_bit_depth['count'] / df_bit_depth['count'].sum()
df_bit_depth
| mode | count | count_rel | |
|---|---|---|---|
| 0 | (R, G, B) | 14291 | 0.866752 |
| 1 | (L,) | 2197 | 0.133248 |
df_bit_depth = df_img_shape_pil['band_mode'].value_counts().reset_index(name='count')
df_bit_depth = df_bit_depth.rename(columns={'index':'mode'})
# add relativ count
df_bit_depth['count_rel'] = df_bit_depth['count'] / df_bit_depth['count'].sum()
df_bit_depth.plot(x='mode', y='count_rel', kind='bar', figsize=(6,4))
plt.suptitle('Verteilung Bit Tiefe Bilder', fontsize=12)
plt.title('RGB: 24bit, L: 8bit', fontsize=8)
plt.xlabel('band mode')
plt.ylabel('count relativ')
plt.xticks(rotation=0)
plt.grid()
if save_plots:
plt.savefig(path_plots + 'dist_image_depth.png', bbox_inches="tight")
plt.show()
Über 80% der Bilder haben 24Bit Farbtiefe (RGB), die restlichen bestehen aus 8Bit (L) für monochrom oder schwarz-weiss Bilder.
df_train_feature_labels_dim = df_train_feature_labels.merge(df_img_shape_pil[['id', 'dim', 'band_mode']], left_index=True, right_on='id')
df_train_feature_labels_dim = df_train_feature_labels_dim.set_index('id')
df_train_feature_labels_dim['dim'] = df_train_feature_labels_dim['dim'].astype(str)
df_train_feature_labels_dim['band_mode'] = df_train_feature_labels_dim['band_mode'].astype(str)
df_train_feature_labels_dim.head()
| filepath | site | label | dim | band_mode | |
|---|---|---|---|---|---|
| id | |||||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 | bird | (960, 540) | ('R', 'G', 'B') |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 | monkey_prosimian | (960, 540) | ('R', 'G', 'B') |
| ZJ000002 | train_features/ZJ000002.jpg | S0009 | bird | (640, 360) | ('R', 'G', 'B') |
| ZJ000003 | train_features/ZJ000003.jpg | S0008 | monkey_prosimian | (640, 360) | ('R', 'G', 'B') |
| ZJ000004 | train_features/ZJ000004.jpg | S0036 | leopard | (640, 335) | ('R', 'G', 'B') |
df_stacked_plot_bit = pd.crosstab(df_train_feature_labels_dim['site'], df_train_feature_labels_dim['band_mode'])
# sort values
df_stacked_plot_bit['max_count'] = df_stacked_plot_bit.sum(axis=1)
df_stacked_plot_bit = df_stacked_plot_bit.sort_values('max_count', ascending=True)
df_stacked_plot_bit
| band_mode | ('L',) | ('R', 'G', 'B') | max_count |
|---|---|---|---|
| site | |||
| S0102 | 0 | 1 | 1 |
| S0078 | 0 | 2 | 2 |
| S0079 | 0 | 2 | 2 |
| S0178 | 0 | 2 | 2 |
| S0143 | 0 | 3 | 3 |
| ... | ... | ... | ... |
| S0036 | 0 | 456 | 456 |
| S0008 | 0 | 541 | 541 |
| S0063 | 0 | 557 | 557 |
| S0009 | 0 | 664 | 664 |
| S0060 | 0 | 1132 | 1132 |
148 rows × 3 columns
min_count = 0
stacked_plot_bit = df_stacked_plot_bit[df_stacked_plot_bit['max_count'] >= min_count].copy()
stacked_plot_bit = stacked_plot_bit.drop(columns='max_count')
print(len(stacked_plot_bit))
stacked_plot_bit.plot(kind='bar', stacked=True, figsize=(20,6))
plt.title('Bild Bittiefe je Standort (Train)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90, fontsize=8)
plt.grid(axis='y')
if save_plots:
plt.savefig(path_plots + 'image_depth_site.png', bbox_inches="tight")
plt.show()
148
Überraschen ist dass die Kamerastandort unterschiedliche Bittiefen haben (Beispiel S0014). Es wurden evtl unterschiedliche Kameras pro Standort verwendet oder die Nachtaufnahmen werden in schwarz-weiss erstellt.
Hier soll untersucht werden ob die schwarz-weiss Aufnahmen von den Tierklassen abhängig sind.
df_train_feature_labels_dim.head(2)
| filepath | site | label | dim | band_mode | |
|---|---|---|---|---|---|
| id | |||||
| ZJ000000 | train_features/ZJ000000.jpg | S0120 | bird | (960, 540) | ('R', 'G', 'B') |
| ZJ000001 | train_features/ZJ000001.jpg | S0069 | monkey_prosimian | (960, 540) | ('R', 'G', 'B') |
for label in species_labels:
df_train_feature_labels_dim_x = df_train_feature_labels_dim[df_train_feature_labels_dim['label'] == label]
df_stacked_plot_bit = pd.crosstab(df_train_feature_labels_dim_x['site'], df_train_feature_labels_dim_x['band_mode'])
# sort values
df_stacked_plot_bit['max_count'] = df_stacked_plot_bit.sum(axis=1)
df_stacked_plot_bit = df_stacked_plot_bit.sort_values('max_count', ascending=True)
df_stacked_plot_bit.tail()
# filter values
min_count = 0
stacked_plot_bit = df_stacked_plot_bit[df_stacked_plot_bit['max_count'] >= min_count].copy()
stacked_plot_bit = stacked_plot_bit.drop(columns='max_count')
# plot
stacked_plot_bit.plot(kind='bar', stacked=True, figsize=(20,5))
plt.title(f'Bild Bittiefe je Standort: {label} (Trainset)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90)
plt.grid(axis='y')
if save_plots:
plt.savefig(f'{path_plots}dist_bitdepth_site_{label}.png', bbox_inches="tight")
plt.tight_layout()
plt.show()
Es scheint dass viele der monochrom Bilder ohne enthaltene Tiere erstellt werden, Klasse 'blank'. Ein Muster für Nachtaktive Tiere kann nicht direkt abgeleitet werden. Festzustellen ist dass die Tierklassen, relative gut verteilt, an vielen Kamera Standorte erfasst werden.
Am unteren Rand haben viele Bilder das gleiche Logo und Zeitstempfel. Auf verschiedenen Bildern wurden diese teilweise durch beschneiden entfernt. Hier soll untersucht werden:
# images with Logo and time:
img_logo_time = ['ZJ000001', 'ZJ000002', 'ZJ000003', 'ZJ000005',
'ZJ000006', 'ZJ000025', 'ZJ000026', 'ZJ000031', 'ZJ000140']
plot_image_from_image_id(img_logo_time, nrows=3, figsize=(10, 7), fontsize=8)
Die neun Testbilder gehören alle zu beiden Hauptgruppen der Bildauflösungen, (640x360) und (960x540)
# images cut
img_logo_time_cut = ['ZJ000004', 'ZJ000053', 'ZJ000063', 'ZJ000090',
'ZJ000099', 'ZJ000106', 'ZJ000114', 'ZJ000119', 'ZJ000156']
plot_image_from_image_id(img_logo_time_cut, nrows=3, figsize=(10, 7), fontsize=8)
Die neun Testbilder, bei denen das Logo und Zeitstempfel teilweise entfernt wurde, haben jeweils eine Beschneidung in der Dimension 'height'. Daraus könnte geschlossen werden dass die bearbeiteten Bilder der Hauptgruppe angehören jedoch mit fehlendem unterem Abschnitt, (640x360) -> (640x335) und (960x540) -> (960x515)
# special cases
img_special_case = ['ZJ000019', 'ZJ000015', 'ZJ000016', 'ZJ000018',
'ZJ000065', 'ZJ000124', 'ZJ000132', 'ZJ000139', 'ZJ000142']
plot_image_from_image_id(img_special_case, nrows=3, figsize=(10, 7), fontsize=8)
def is_overexposed(image_path, threshold=220):
'''
high threshold for high brightness
'''
# Öffnen des Bildes mit Pillow
image = Image.open(image_path)
# Berechnung der durchschnittlichen Helligkeit des Bildes
brightness = sum(image.convert('L').getdata()) / (image.width * image.height)
# Überprüfung, ob die Helligkeit über dem Schwellenwert liegt
return brightness > threshold
images_overexposed = {}
for im_path in train_features['filepath'].head(100):
path = path_img + im_path
# check image is overexposed
if is_overexposed(path, threshold=220):
image_name = os.path.split(path)[1].split('.')[0]
images_overexposed[image_name] = im_path
print(f'Found {len(images_overexposed)} Images')
# plot images
images_overexposed_ids = list(images_overexposed.keys())
plot_image_from_image_id(images_overexposed_ids, nrows=1, figsize=(10, 7), fontsize=8)
Found 3 Images
display(df_site_count[df_site_count['filepath'] > 600])
| site | filepath | |
|---|---|---|
| 8 | S0009 | 664 |
| 46 | S0060 | 1132 |
site_S0060 = 'S0060'
plot_image_from_site_id(site_S0060)
Erkenntnisse der manuellen Untersuchung der Bilder:
Civet Genet an unterschiedlichen Bildorten abgebildet. Kamera evtl direkt neben Civet Genet Bau oder in dessen Revier aufgebautBurst-Effekts ist also schwierig.# following code copys the site images from S0060 to the eda folder
create_folder_S0060_with_images = False
# Filter for S0060 and civet_genet
filter_S0060 = train_features_label[(train_features_label['site'] == site_S0060) & (train_features_label['label'] == 'civet_genet')]['filepath']
# get image names
S0060_images = filter_S0060.str.split('/', expand=True)[1]
S0060_images
if create_folder_S0060_with_images:
# create folder for site S0060
folder_name = "site_S0060"
folder_S0060_path = "./"
folder_S0060_path = os.path.join(folder_S0060_path, folder_name)
if not os.path.exists(folder_S0060_path):
os.makedirs(folder_S0060_path)
# Copy S0060 images to folder ./S0060
source_path = '../competition_data/train_features/'
for filename in os.listdir('../competition_data/train_features/'):
if filename in list(S0060_images):
#shutil.copy(source_path, folder_S0060_path)
filname_id = filename[:-4]
img = mpimg.imread(path_img + train_features.loc[filname_id].filepath)
mpimg.imsave(folder_S0060_path + '/' + filename, img)
Untersuchungen zur Überbelichtung
Auch mit tieferem treshold=200 wurden keine Überbelichtete Bilder gefunden. Falls die Bilder zu Civet Genet reduziert werden sollen, eignet sich die Prüfung nach der Helligkeit der Bilder nicht. Eine zufällige manuelle Ansicht zeigte dass Civet Genet auf den hellen Bildern teilweise besser ersichtlich ist.
images_overexposed_S0060 = {}
for im_path in filter_S0060:
path = path_img + im_path
# check image is overexposed
if is_overexposed(path, threshold=200):
image_name = os.path.split(path)[1].split('.')[0]
images_overexposed[image_name] = im_path
print(f'Found {len(images_overexposed_S0060)} overexposed Images')
# plot images
#images_overexposed_S0060_ids = list(images_overexposed_S0060.keys())
#plot_image_from_image_id(images_overexposed_S0060_ids, nrows=1, figsize=(10, 7), fontsize=8)
Found 0 overexposed Images
Fazit: Falls die Anzahl der Tierklasse Civet Genet für den Standort S0060 begrenzt werden möchte, kann dies zufällig gemacht werden.
list(images_overexposed.keys())
['ZJ000007', 'ZJ000059', 'ZJ000084']
for image_id, path in images_overexposed.items():
path = path_img + path
img = Image.open(path)
img.save(f"./enhanced_images/{image_id}_pre_enh.jpg")
enhancer = ImageEnhance.Brightness(img)
# to reduce brightness by 50%, use factor 0.5
img = enhancer.enhance(0.5)
img.save(f"./enhanced_images/{image_id}_post_enh.jpg")
Hier soll versucht werden ob das Logo im linken unteren Teil des Bildes automatisch erkannt werden kann
# crop image
def crop_image_for_logo(pillow_image, plot_image=False):
# dim img
width, height = img.size
# crop images, left corner
crop_height = height * 0.09
crop_width = width * 0.95
x1 = 0
y1 = height - crop_height
x2 = width - crop_width
y2 = height
cropped_img = img.crop((x1, y1, x2, y2))
if plot_image:
plt.imshow(cropped_img)
plt.show()
return cropped_img
# get image main color
def get_img_main_color(pillow_imag, print_info=False):
# Wandle das Bild in den RGB-Modus um, falls es noch nicht im RGB-Modus vorliegt.
if pillow_imag.mode != "RGB":
pillow_imag = pillow_imag.convert("RGB")
# Extrahiere die Farb-Kanäle aus dem Bild.
r, g, b = pillow_imag.split()[0], pillow_imag.split()[1], pillow_imag.split()[2]
# Berechne den durchschnittlichen Wert des Kanals.
mean_r = sum(r.getdata()) / len(r.getdata())
mean_g = sum(g.getdata()) / len(g.getdata())
mean_b = sum(b.getdata()) / len(b.getdata())
mean_rgb = {'red': mean_r, 'green': mean_g, 'blue': mean_b}
max_mean_color = max(mean_rgb, key=lambda x:mean_rgb[x])
if print_info:
print(f'image is mostly: {max_mean_color}')
return
return max_mean_color
# read image
img = Image.open(path_img + train_features.loc['ZJ000001'].filepath)
# crop image
cropped_image = crop_image_for_logo(img, plot_image=True)
# get main color image
get_img_main_color(cropped_image, print_info=True)
image is mostly: red
df_for_cross_split = df_site_count.reset_index(drop=True)
bins = np.append(0,np.sort(np.append(148,148-np.cumsum((148//5 + 1) * [5])))[1:])
df_for_cross_split=pd.concat([df_for_cross_split,pd.DataFrame(pd.cut(df_for_cross_split.index, bins=bins, include_lowest=True))],axis=1)
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
#added some parameters
n_animals = []
splits=[]
kf=StratifiedKFold(n_splits = 5, shuffle = True,random_state=49)
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(df_for_cross_split[0])
for n,(train,test) in enumerate(kf.split(np.zeros(len(y)),y)):
n_animals.append(np.sum(df_for_cross_split.iloc[test]["filepath"]))
df_for_cross_split.loc[test,"split"] = n
c:\Users\manue\.virtualenvs\tierli_ahluege-kxyn7EyL\lib\site-packages\sklearn\model_selection\_split.py:700: UserWarning: The least populated class in y has only 4 members, which is less than n_splits=5. warnings.warn(
cross_val_train_feature_labels = pd.merge(df_train_feature_labels_dim,df_for_cross_split.loc[:,["site","split"]],on="site")
df_stacked_plot_bit = pd.crosstab(cross_val_train_feature_labels['split'], cross_val_train_feature_labels['band_mode'])
df_stacked_plot_bit = df_stacked_plot_bit.div(df_stacked_plot_bit.sum(1), axis=0).mul(100)
df_stacked_plot_bit.plot(kind='bar', stacked=True, figsize=(20,6))
plt.title('Bild Bittiefe je Standort (Train)', fontsize=20)
plt.xlabel('split_id')
plt.ylabel('counts')
plt.xticks(rotation=90, fontsize=8)
plt.grid(axis='y')
if save_plots:
plt.savefig(path_plots + 'image_depth_site.png', bbox_inches="tight")
plt.show()
df_stacked_plot = pd.crosstab(cross_val_train_feature_labels['split'], cross_val_train_feature_labels['label'])
df_stacked_plot = df_stacked_plot.div(df_stacked_plot.sum(1), axis=0).mul(100)
df_stacked_plot.plot(kind='bar', stacked=True, figsize=(20,8))
plt.title('Vorkommen Tierklassen je Standort (Train)', fontsize=20)
plt.xlabel('split')
plt.ylabel('Percentage per split')
plt.xticks(rotation=90)
plt.grid(axis='y')
if save_plots:
plt.savefig(path_plots + 'dist_animals_n_images_site.png', bbox_inches="tight")
plt.show()
train_features_with_split = pd.merge(train_features.reset_index(),cross_val_train_feature_labels.loc[:,["filepath","split"]],on="filepath",how= "left")
train_labels_with_split = pd.merge(train_labels.reset_index(),train_features_with_split.loc[:,["id","split"]],on="id")
if split_cv_csv:
train_features_with_split.to_csv("../competition_data/train_features_with_split.csv",index=False)
train_labels_with_split.to_csv("../competition_data/train_labels_with_split.csv",index=False)